package DAL;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import lejos.pc.comm.NXTComm;
import lejos.pc.comm.NXTConnector;
import lejos.pc.comm.NXTInfo;
import sharedData.ADataItem;
import sharedData.DebugItem;
import sharedData.MapData;
import sharedData.SharedConstants;
import BE.NXTDataEvent;
import BE.NXTListener;

/**
 * Handles bluetooth communication between a PC client and a NXT brick.
 * Creates a thread away from the EDT and listens for incoming data and
 * sends events to a dedicated listener.
 * 
 * @author Danny Johansen, Didzis Petkus, Liviu Berheci.
 * 
 */
public class BTCommunicator {

	private NXTConnector connector;
	private DataInputStream is;
	private DataOutputStream os;
	private byte[] data;
	private NXTListener listener;
	private Thread listenThread;

	/**
	 * Consructor assigning a passed custom listener object as an
	 * internal event handler.
	 * @param l - the passed NXTListener to assign as event handler.
	 */
	public BTCommunicator(NXTListener l) {
		connector = new NXTConnector();
		this.listener = l;
		listenThread = new Thread(new Runnable() {

			@Override
			public void run() {
				ByteBuffer buffer;
				float x;
				float y;
				float angle;
				float leftUSSR;
				float rightUSSR;
				float centerUSSR;
				ADataItem dataItem;

				while (is != null) {
					try {
						data = new byte[256];
						is.read(data);
						byte header = data[0];

						buffer = ByteBuffer.wrap(data, 1, data.length - 1);

						if (header == SharedConstants.DATA_TYPE_MAP_DATA) {
							x = buffer.getFloat();
							y = buffer.getFloat();
							angle = buffer.getFloat();
							leftUSSR = buffer.getFloat();
							rightUSSR = buffer.getFloat();
							centerUSSR = buffer.getFloat();

							dataItem = new MapData(header, x, y, angle,
									leftUSSR, rightUSSR, centerUSSR);
							listener.NXTMapDataReceived(new NXTDataEvent(this,
									connector.getNXTInfo(), dataItem));
						} else if (header == SharedConstants.DATA_TYPE_DEBUG) {
							x = buffer.getFloat();
							y = buffer.getFloat();
							angle = buffer.getFloat();

							byte[] bytearr = new byte[buffer.remaining()];
							buffer.get(bytearr);
							String msg = new String(bytearr).trim();

							dataItem = new DebugItem(header, x, y, angle, msg);
							listener.NXTMapDataReceived(new NXTDataEvent(this,
									connector.getNXTInfo(), dataItem));
						}

					} catch (IOException e) {
						// TODO: handle exception
					}
				}
			}
		});
	}

	/**
	 * Connects to a NXT brick matching the passed NXTInfo bean information
	 * and open input and output streams.
	 * @param deviceInfo - the passed NXTInfo java bean.
	 * @return <b>true</b> - on successful connection.
	 * <br> <b>false</b> - otherwise.
	 */
	public boolean connnectDevice(NXTInfo deviceInfo) {

		boolean connected = connector.connectTo(deviceInfo, NXTComm.LCP);

		if (connected) {
			openStreams();
		}

		return connected;
	}

	/**
	 * Disconnects a NXT brick matching the passed NXTInfo bean information
	 * and closes any open streams.
	 * @param deviceInfo - the passed NXTInfo java bean.
	 * @throws IOException - if an I/O error occurs.
	 */
	public void disconnectDevice(NXTInfo deviceInfo) throws IOException {
		closeStreams();
		connector.close();
	}

	/**
	 * Returns a NXTInfo object this representing the connected NXT device.
	 * @return - the stored NXTInfo Java Bean.
	 */
	public NXTInfo getDeviceInfo() {
		return connector.getNXTInfo();
	}

	/**
	 * Starts the internal thread. 
	 * @throws IOException - if an I/O error occurs.
	 */
	public void ListenForInput() throws IOException {
		listenThread.start();
	}

	/**
	 * Opens an input and output stream for the connection.
	 */
	private void openStreams() {
		if (is == null) {
			is = new DataInputStream(connector.getInputStream());
		}
		if (os == null) {
			os = new DataOutputStream(connector.getOutputStream());
		}
	}

	/**
	 * Closes the input and output stream of the connection.
	 * @throws IOException - if an I/O error occurs.
	 */
	private void closeStreams() throws IOException {
		if (is != null) {
			is.close();
		}
		if (os != null) {
			os.close();
		}
	}
}
